/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.client;
import elemental.client.Browser;
import elemental.events.Event;
import elemental.events.EventListener;
import org.eclipse.che.api.analytics.logger.EventLogger;
import org.eclipse.che.api.factory.dto.Factory;
import org.eclipse.che.api.factory.dto.Ide;
import org.eclipse.che.api.factory.gwt.client.FactoryServiceClient;
import org.eclipse.che.api.project.gwt.client.ProjectTemplateServiceClient;
import org.eclipse.che.api.project.gwt.client.ProjectTypeServiceClient;
import org.eclipse.che.api.project.shared.dto.ProjectTemplateDescriptor;
import org.eclipse.che.api.project.shared.dto.ProjectTypeDefinition;
import org.eclipse.che.api.user.gwt.client.UserProfileServiceClient;
import org.eclipse.che.api.user.shared.dto.ProfileDescriptor;
import org.eclipse.che.api.workspace.gwt.client.WorkspaceServiceClient;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDescriptor;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.Presentation;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.app.CurrentUser;
import org.eclipse.che.ide.api.event.OpenProjectEvent;
import org.eclipse.che.ide.api.event.ProjectActionEvent;
import org.eclipse.che.ide.api.event.ProjectActionHandler;
import org.eclipse.che.ide.api.event.WindowActionEvent;
import org.eclipse.che.ide.api.icon.Icon;
import org.eclipse.che.ide.api.icon.IconRegistry;
import org.eclipse.che.ide.api.project.type.ProjectTemplateRegistry;
import org.eclipse.che.ide.api.project.type.ProjectTypeRegistry;
import org.eclipse.che.ide.api.theme.Style;
import org.eclipse.che.ide.api.theme.Theme;
import org.eclipse.che.ide.api.theme.ThemeAgent;
import org.eclipse.che.ide.collections.Array;
import org.eclipse.che.ide.core.ComponentException;
import org.eclipse.che.ide.core.ComponentRegistry;
import org.eclipse.che.ide.logger.AnalyticsEventLoggerExt;
import org.eclipse.che.ide.preferences.PreferencesManagerImpl;
import org.eclipse.che.ide.rest.AsyncRequestCallback;
import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
import org.eclipse.che.ide.rest.StringMapUnmarshaller;
import org.eclipse.che.ide.toolbar.PresentationFactory;
import org.eclipse.che.ide.util.Config;
import org.eclipse.che.ide.util.UUID;
import org.eclipse.che.ide.util.loging.Log;
import org.eclipse.che.ide.workspace.WorkspacePresenter;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Performs initial application startup.
*
* @author Nikolay Zamosenchuk
*/
public class BootstrapController {
private final DtoUnmarshallerFactory dtoUnmarshallerFactory;
private final AnalyticsEventLoggerExt analyticsEventLoggerExt;
private final IconRegistry iconRegistry;
private final ThemeAgent themeAgent;
private final Provider<ComponentRegistry> componentRegistry;
private final Provider<WorkspacePresenter> workspaceProvider;
private final ExtensionInitializer extensionInitializer;
private final FactoryServiceClient factoryService;
private final UserProfileServiceClient userProfileService;
private final WorkspaceServiceClient workspaceServiceClient;
private final ProjectTypeServiceClient projectTypeService;
private final ProjectTemplateServiceClient projectTemplateServiceClient;
private final ProjectTypeRegistry projectTypeRegistry;
private final ProjectTemplateRegistry projectTemplateRegistry;
private final PreferencesManagerImpl preferencesManager;
private final StyleInjector styleInjector;
private final CoreLocalizationConstant coreLocalizationConstant;
private final EventBus eventBus;
private final ActionManager actionManager;
private final AppCloseHandler appCloseHandler;
private final PresentationFactory presentationFactory;
private final AppContext appContext;
private CurrentUser currentUser;
/** Create controller. */
@Inject
public BootstrapController(Provider<ComponentRegistry> componentRegistry,
Provider<WorkspacePresenter> workspaceProvider,
ExtensionInitializer extensionInitializer,
UserProfileServiceClient userProfileService,
WorkspaceServiceClient workspaceServiceClient,
ProjectTypeServiceClient projectTypeService,
ProjectTemplateServiceClient projectTemplateServiceClient,
ProjectTypeRegistry projectTypeRegistry,
ProjectTemplateRegistry projectTemplateRegistry,
PreferencesManagerImpl preferencesManager,
StyleInjector styleInjector,
CoreLocalizationConstant coreLocalizationConstant,
DtoRegistrar dtoRegistrar,
DtoUnmarshallerFactory dtoUnmarshallerFactory,
AnalyticsEventLoggerExt analyticsEventLoggerExt,
Resources resources,
FactoryServiceClient factoryService,
EventBus eventBus,
AppContext appContext,
final IconRegistry iconRegistry,
final ThemeAgent themeAgent,
ActionManager actionManager,
AppCloseHandler appCloseHandler) {
this.componentRegistry = componentRegistry;
this.workspaceProvider = workspaceProvider;
this.extensionInitializer = extensionInitializer;
this.userProfileService = userProfileService;
this.workspaceServiceClient = workspaceServiceClient;
this.projectTypeService = projectTypeService;
this.projectTemplateServiceClient = projectTemplateServiceClient;
this.projectTypeRegistry = projectTypeRegistry;
this.projectTemplateRegistry = projectTemplateRegistry;
this.preferencesManager = preferencesManager;
this.styleInjector = styleInjector;
this.coreLocalizationConstant = coreLocalizationConstant;
this.factoryService = factoryService;
this.eventBus = eventBus;
this.appContext = appContext;
this.iconRegistry = iconRegistry;
this.themeAgent = themeAgent;
this.actionManager = actionManager;
this.dtoUnmarshallerFactory = dtoUnmarshallerFactory;
this.analyticsEventLoggerExt = analyticsEventLoggerExt;
this.appCloseHandler = appCloseHandler;
presentationFactory = new PresentationFactory();
// Register DTO providers
dtoRegistrar.registerDtoProviders();
// Register default icons
registerDefaultIcons(resources);
// Inject ZeroClipboard script
ScriptInjector.fromUrl(GWT.getModuleBaseForStaticFiles() + "ZeroClipboard.min.js").setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(new Callback<Void, Exception>() {
@Override
public void onSuccess(Void result) {
}
@Override
public void onFailure(Exception e) {
Log.error(getClass(), "Unable to inject ZeroClipboard.min.js", e);
}
}).inject();
currentUser = new CurrentUser();
loadWorkspace();
}
/**
* Fetches current workspace and saves it to Config.
*/
private void loadWorkspace() {
workspaceServiceClient.getWorkspace(Config.getWorkspaceId(),
new AsyncRequestCallback<WorkspaceDescriptor>(
dtoUnmarshallerFactory.newUnmarshaller(WorkspaceDescriptor.class)) {
@Override
protected void onSuccess(WorkspaceDescriptor result) {
Config.setCurrentWorkspace(result);
appContext.setWorkspace(result);
loadUserProfile();
}
@Override
protected void onFailure(Throwable throwable) {
Log.error(BootstrapController.class, "Unable to get Workspace", throwable);
initializationFailed("Unable to get Workspace");
}
}
);
}
/** Get User profile, restore preferences and theme */
private void loadUserProfile() {
userProfileService.getCurrentProfile(new AsyncRequestCallback<ProfileDescriptor>(
dtoUnmarshallerFactory.newUnmarshaller(ProfileDescriptor.class)) {
@Override
protected void onSuccess(final ProfileDescriptor profile) {
currentUser.setProfile(profile);
loadPreferences();
}
@Override
protected void onFailure(Throwable error) {
Log.error(BootstrapController.class, "Unable to get Profile", error);
initializationFailed("Unable to get Profile");
}
}
);
}
private void loadPreferences() {
userProfileService.getPreferences(new AsyncRequestCallback<Map<String, String>>(new StringMapUnmarshaller()) {
@Override
protected void onSuccess(Map<String, String> preferences) {
currentUser.setPreferences(preferences);
appContext.setCurrentUser(currentUser);
preferencesManager.load(preferences);
setTheme();
styleInjector.inject();
loadProjectTypes();
}
@Override
protected void onFailure(Throwable exception) {
Log.error(BootstrapController.class, "Unable to load user preferences", exception);
initializationFailed("Unable to load preferences");
}
});
}
private void loadProjectTypes() {
projectTypeService.getProjectTypes(
new AsyncRequestCallback<Array<ProjectTypeDefinition>>(
dtoUnmarshallerFactory.newArrayUnmarshaller(ProjectTypeDefinition.class)) {
@Override
protected void onSuccess(Array<ProjectTypeDefinition> result) {
for (ProjectTypeDefinition projectType : result.asIterable()) {
projectTypeRegistry.register(projectType);
}
loadProjectTemplates();
}
@Override
protected void onFailure(Throwable exception) {
Log.error(BootstrapController.class, exception);
}
});
}
private void loadProjectTemplates() {
projectTemplateServiceClient.getProjectTemplates(new AsyncRequestCallback<Array<ProjectTemplateDescriptor>>(
dtoUnmarshallerFactory.newArrayUnmarshaller(ProjectTemplateDescriptor.class)) {
@Override
protected void onSuccess(Array<ProjectTemplateDescriptor> result) {
for (ProjectTemplateDescriptor template : result.asIterable()) {
projectTemplateRegistry.register(template);
}
loadFactory();
}
@Override
protected void onFailure(Throwable exception) {
Log.error(BootstrapController.class, exception);
}
});
}
private void loadFactory() {
String factoryParams = null;
boolean encoded = false;
if (Config.getStartupParam("id") != null) {
factoryParams = Config.getStartupParam("id");
encoded = true;
} else if (Config.getStartupParam("v") != null) {
factoryParams = Config.getStartupParams();
}
if (factoryParams != null) {
factoryService.getFactory(factoryParams, encoded,
new AsyncRequestCallback<Factory>(dtoUnmarshallerFactory.newUnmarshaller(Factory.class)) {
@Override
protected void onSuccess(Factory factory) {
appContext.setFactory(factory);
initializeComponentRegistry();
}
@Override
protected void onFailure(Throwable error) {
Log.error(BootstrapController.class, "Unable to load Factory", error);
initializeComponentRegistry();
}
}
);
} else {
initializeComponentRegistry();
}
}
/** Initialize Component Registry, start extensions */
private void initializeComponentRegistry() {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
componentRegistry.get().start(new Callback<Void, ComponentException>() {
@Override
public void onSuccess(Void result) {
// Instantiate extensions
extensionInitializer.startExtensions();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
displayIDE();
}
});
}
@Override
public void onFailure(ComponentException caught) {
Log.error(BootstrapController.class, "Unable to start component " + caught.getComponent(), caught);
initializationFailed("Unable to start component " + caught.getComponent());
}
});
}
});
}
/** Displays the IDE */
private void displayIDE() {
// Start UI
SimpleLayoutPanel mainPanel = new SimpleLayoutPanel();
RootLayoutPanel.get().add(mainPanel);
// Make sure the root panel creates its own stacking context
RootLayoutPanel.get().getElement().getStyle().setZIndex(0);
WorkspacePresenter workspacePresenter = workspaceProvider.get();
// Display 'Update extension' button if IDE is launched in SDK runner
workspacePresenter.setUpdateButtonVisibility(Config.getStartupParam("h") != null && Config.getStartupParam("p") != null);
// Display IDE
workspacePresenter.go(mainPanel);
Document.get().setTitle(coreLocalizationConstant.codenvyTabTitle());
processStartupParameters();
final AnalyticsSessions analyticsSessions = new AnalyticsSessions();
// Bind browser's window events
Window.addWindowClosingHandler(new Window.ClosingHandler() {
@Override
public void onWindowClosing(Window.ClosingEvent event) {
onWindowClose(analyticsSessions);
eventBus.fireEvent(WindowActionEvent.createWindowClosingEvent(event));
}
});
Window.addCloseHandler(new CloseHandler<Window>() {
@Override
public void onClose(CloseEvent<Window> event) {
onWindowClose(analyticsSessions);
eventBus.fireEvent(WindowActionEvent.createWindowClosedEvent());
}
});
elemental.html.Window window = Browser.getWindow();
window.addEventListener(Event.FOCUS, new EventListener() {
@Override
public void handleEvent(Event evt) {
onSessionUsage(analyticsSessions, false);
}
}, true);
window.addEventListener(Event.BLUR, new EventListener() {
@Override
public void handleEvent(Event evt) {
onSessionUsage(analyticsSessions, false);
}
}, true);
onSessionUsage(analyticsSessions, true); // This is necessary to forcibly print the very first event
}
private void onSessionUsage(AnalyticsSessions analyticsSessions, boolean force) {
if (analyticsSessions.getIdleUsageTime() > 600000) { // 10 min
analyticsSessions.makeNew();
logSessionUsageEvent(analyticsSessions, true);
} else {
logSessionUsageEvent(analyticsSessions, force);
analyticsSessions.updateUsageTime();
}
}
private void onWindowClose(AnalyticsSessions analyticsSessions) {
if (analyticsSessions.getIdleUsageTime() <= 60000) { // 1 min
logSessionUsageEvent(analyticsSessions, true);
analyticsSessions.updateUsageTime();
}
}
private void logSessionUsageEvent(AnalyticsSessions analyticsSessions, boolean force) {
if (force || analyticsSessions.getIdleLogTime() > 60000) { // 1 min, don't log frequently than once per minute
Map<String, String> parameters = new HashMap<>();
parameters.put("SESSION-ID", analyticsSessions.getId());
analyticsEventLoggerExt.logEvent(EventLogger.SESSION_USAGE, parameters);
if (Config.getCurrentWorkspace() != null && Config.getCurrentWorkspace().isTemporary()) {
analyticsEventLoggerExt.logEvent(EventLogger.SESSION_FACTORY_USAGE, parameters);
}
analyticsSessions.updateLogTime();
}
}
HandlerRegistration handlerRegistration = null;
private void processStartupParameters() {
final String projectNameToOpen = Config.getProjectName();
if (projectNameToOpen != null) {
handlerRegistration = eventBus.addHandler(ProjectActionEvent.TYPE, getStartupActionHandler());
eventBus.fireEvent(new OpenProjectEvent(projectNameToOpen));
} else {
processStartupAction();
handlerRegistration = eventBus.addHandler(ProjectActionEvent.TYPE, getFactoryActionHandler());
}
if (appContext.getFactory() != null && appContext.getFactory().getIde() != null) {
final Ide ide = appContext.getFactory().getIde();
if (ide.getOnAppClosed() != null && ide.getOnAppClosed().getActions() != null) {
appCloseHandler.performBeforeClose(ide.getOnAppClosed().getActions());
}
if (ide.getOnAppLoaded() != null && ide.getOnAppLoaded().getActions() != null) {
performActions(ide.getOnAppLoaded().getActions());
}
}
}
private ProjectActionHandler getFactoryActionHandler() {
return new ProjectActionHandler() {
@Override
public void onProjectOpened(ProjectActionEvent event) {
if (handlerRegistration != null) {
handlerRegistration.removeHandler();
}
if (appContext.getFactory() != null && appContext.getFactory().getIde() != null
&& appContext.getFactory().getIde().getOnProjectOpened() != null
&& appContext.getFactory().getIde().getOnProjectOpened().getActions() != null) {
performActions(appContext.getFactory().getIde().getOnProjectOpened().getActions());
}
}
@Override
public void onProjectClosed(ProjectActionEvent event) {
//do nothing
}
};
}
private ProjectActionHandler getStartupActionHandler() {
return new ProjectActionHandler() {
//process action only after opening project
@Override
public void onProjectOpened(ProjectActionEvent event) {
processStartupAction();
}
@Override
public void onProjectClosed(ProjectActionEvent event) {
}
};
}
private void processStartupAction() {
final String startupAction = Config.getStartupParam("action");
if (startupAction != null) {
performAction(startupAction);
}
}
private void performActions(List<org.eclipse.che.api.factory.dto.Action> actions) {
for (org.eclipse.che.api.factory.dto.Action action : actions) {
performAction(action.getId(), action.getProperties());
}
}
private void performAction(String actionId) {
performAction(actionId, null);
}
private void performAction(String actionId, Map<String, String> parameters) {
Action action = actionManager.getAction(actionId);
if (action == null) {
return;
}
final Presentation presentation = presentationFactory.getPresentation(action);
ActionEvent e = new ActionEvent("", presentation, actionManager, 0, parameters);
action.update(e);
if (presentation.isEnabled() && presentation.isVisible()) {
action.actionPerformed(e);
}
}
/** Applying user defined Theme. */
private void setTheme() {
String storedThemeId = preferencesManager.getValue("Theme");
storedThemeId = storedThemeId != null ? storedThemeId : themeAgent.getCurrentThemeId();
Theme themeToSet = storedThemeId != null ? themeAgent.getTheme(storedThemeId) : themeAgent.getDefault();
Style.setTheme(themeToSet);
themeAgent.setCurrentThemeId(themeToSet.getId());
}
private void registerDefaultIcons(Resources resources) {
iconRegistry.registerIcon(new Icon("default.projecttype.small.icon", "default/project.png", resources.defaultProject()));
iconRegistry.registerIcon(new Icon("default.folder.small.icon", "default/folder.png", resources.defaultFolder()));
iconRegistry.registerIcon(new Icon("default.file.small.icon", "default/file.png", resources.defaultFile()));
iconRegistry.registerIcon(new Icon("default", "default/default.jpg", resources.defaultIcon()));
}
/**
* Handles any of initialization errors.
* Tries to call predefined IDE.eventHandlers.ideInitializationFailed function.
*
* @param message
* error message
*/
private native void initializationFailed(String message) /*-{
try {
$wnd.IDE.eventHandlers.initializationFailed(message);
} catch (e) {
console.log(e.message);
}
}-*/;
private static class AnalyticsSessions {
private String id;
private long lastLogTime;
private long lastUsageTime;
private AnalyticsSessions() {
makeNew();
}
public String getId() {
return id;
}
public void updateLogTime() {
lastLogTime = System.currentTimeMillis();
}
public void updateUsageTime() {
lastUsageTime = System.currentTimeMillis();
}
public void makeNew() {
this.id = UUID.uuid();
this.lastUsageTime = System.currentTimeMillis();
this.lastLogTime = lastUsageTime;
}
public long getIdleUsageTime() {
return System.currentTimeMillis() - lastUsageTime;
}
public long getIdleLogTime() {
return System.currentTimeMillis() - lastUsageTime;
}
}
}